package com.example.myapplication2;

import java.nio.*;
import java.nio.channels.*;
import java.util.*;
import java.lang.*;
import java.net.InetSocketAddress;

class sockData {
    boolean peerFull, tellMyFull, peerClose;
    myBuf data;
    byte[] id;

    sockData(byte[] d) {
        peerClose = peerFull = tellMyFull = false;
        data = new myBuf();
        id = d;
    }
}

public class connc {
    ServerSocketChannel accepSock, controlSock, fileSock;
    SocketChannel dataSock, numSock;
    Map<String, SocketChannel> idMap = new HashMap<String, SocketChannel>();
    Map<SocketChannel, sockData> proxySocks = new HashMap<SocketChannel, sockData>();
    myBuf peerBuf = new myBuf(1000), readCache = new myBuf(1000),
            numSockBuf = new myBuf(1000);
    int connNum = 0;
    int maxNum = helpFunc.con_maxConn;
    byte[] decodeData;
    byte[] decodeId;
    int decodeType;
    int askConn_g = 1;
    int connBack_g = 2;
    int full_g = 3;
    int notfull_g = 4;
    int data_g = 5;
    int close_g = 6;
    int bufChange_g = 7;
    int file_g = 8;
    int ping_g = 9;
    int pong_g = 10;
    int sockWriteLen = helpFunc.con_connBufSize;
    int lastBufSize = helpFunc.con_connBufSize;
    int controlPort, cliControlPort;
    int cachePerConn = helpFunc.con_cachePerConn;
    logClass myLog;

    public connc(LinkedList<Integer> ports, logClass lo)
            throws Exception {
        myLog = lo;
        cli a = new cli(ports, lo);
        cliControlPort = a.controlPort;
        myThr aa = new myThr();
        aa.a = a;
        aa.setDaemon(true);
        aa.start();
        dataSock = SocketChannel.open();
        InetSocketAddress add = new InetSocketAddress("localhost", a.dataPort);
        dataSock.connect(add);
        numSock = SocketChannel.open();
        InetSocketAddress add2 = new InetSocketAddress("localhost", a.numPort);
        numSock.connect(add2);
        add = new InetSocketAddress("0.0.0.0", helpFunc.con_localPort);
        accepSock = ServerSocketChannel.open();
        accepSock.configureBlocking(false);
        accepSock.bind(add);
        dataSock.configureBlocking(false);
        numSock.configureBlocking(false);
        iniControlSock();
    }

    void start() {
        while (true) {
            try {
                doWork();
            } catch (Exception e) {
                e.printStackTrace();
                myLog.setLog(e.toString());
            }
        }
    }

    void dealBufChange(int a) {
        int ch = a - lastBufSize;
        lastBufSize = a;
        sockWriteLen += ch;
        String st = "bufSizeChanged " + lastBufSize + " " + sockWriteLen;
        helpFunc.print(st);
        myLog.setLog(st);
    }

    void iniControlSock() throws Exception {
        controlSock = ServerSocketChannel.open();
        controlPort = helpFunc.bindAnyPort(controlSock);
        controlSock.configureBlocking(false);
        fileSock = ServerSocketChannel.open();
        InetSocketAddress add = new InetSocketAddress("localhost", helpFunc.con_localPort + 1);
        fileSock.bind(add);
        fileSock.configureBlocking(false);
    }

    void rig(Selector s) throws Exception {
        for (SocketChannel sock : proxySocks.keySet()) {
            sockData d = proxySocks.get(sock);
            int k = 0;
            if (peerBuf.size() < helpFunc.tcpReadLen && (!d.peerFull && (!d.peerClose))) {
                k |= SelectionKey.OP_READ;
            }
            if (d.data.size() > 0) {
                k |= SelectionKey.OP_WRITE;
            }
            if (k != 0) {
                sock.register(s, k);
            }
        }
    }

    void numSockRecv() throws Exception {
        byte[] xx = helpFunc.tcpRead(numSock);
        numSockBuf.append(xx);
        while (numSockBuf.size() >= 4) {
            sockWriteLen += numSockBuf.readDWord();
        }
    }

    void dealAccep() throws Exception {
        SocketChannel sock = accepSock.accept();
        sock.configureBlocking(false);
        connNum += 1;
        helpFunc.print("ask conn " + idMap.size());
        if (connNum > maxNum) {
            sock.close();
            connNum -= 1;
            helpFunc.print("too much conn");
        } else {
            byte[] id = helpFunc.getuuid_byte();
            sock.configureBlocking(false);
            idMap.put(helpFunc.bytesToHex(id), sock);
            byte[] msg = makeMsg(askConn_g, id, new byte[1]);
            peerBuf.append(msg);
        }
    }

    byte[] makeMsg(int type, byte[] id, byte[] data) {
        myBuf w = new myBuf(5 + id.length + data.length);
        w.writeDWord(5 + id.length + data.length);
        w.writeByte(type);
        w.writeArr(id);
        w.writeArr(data);
        return w.get();
    }

    void decodeMsg(byte[] msg) {
        myBuf w = new myBuf(msg);
        decodeType = w.readByte();
        decodeId = w.readArr(16);
        decodeData = w.readArr(msg.length - 17);
    }

    void dealConnSockRead() throws Exception {
        readCache.append(helpFunc.tcpRead(dataSock));
        ArrayList<byte[]> ll = dealData();
        for (byte[] msg : ll) {
            decodeMsg(msg);
            if (decodeType == file_g) {
                myLog.write(decodeData);
                myLog.setLog("got file");
            }
            else if(decodeType == ping_g)
            {
                peerBuf.append(makeMsg(ping_g,new byte[16],new byte[1]));
            }
            else if(decodeType == pong_g)
            {
                myLog.setLog("got pong");
            }
            else if (decodeType == connBack_g) {
                byte ok = decodeData[0];
                SocketChannel sock = idMap.get(helpFunc.bytesToHex(decodeId));
                if (ok == 1) {
                    sockData d = new sockData(decodeId);
                    proxySocks.put(sock, d);
                    helpFunc.print("connBack ok");
                } else {
                    connNum -= 1;
                    sock.close();
                    idMap.remove(helpFunc.bytesToHex(decodeId));
                    helpFunc.print("connBack fail");
                }
            } else {
                dealNormalConnSock();
            }
        }
    }

    void dealNormalConnSock() {
        if (!idMap.containsKey(helpFunc.bytesToHex(decodeId))) return;
        SocketChannel sock = idMap.get(helpFunc.bytesToHex(decodeId));
        if (decodeType == full_g)
            proxySocks.get(sock).peerFull = true;
        if (decodeType == notfull_g)
            proxySocks.get(sock).peerFull = false;
        if (decodeType == data_g) {
            proxySocks.get(sock).data.append(decodeData);
            if (proxySocks.get(sock).data.size() > cachePerConn && (!proxySocks.get(sock).tellMyFull)) {
                byte[] msg = makeMsg(full_g, decodeId, new byte[1]);
                proxySocks.get(sock).tellMyFull = true;
                peerBuf.append(msg);
            }
        }
        if (decodeType == close_g) {
            proxySocks.get(sock).peerClose = true;
            if (proxySocks.get(sock).data.size() == 0) {
                doClose(sock, decodeId);
            }
        }
    }

    void doClose(SocketChannel sock, byte[] id) {
        try {
            sock.close();
            connNum -= 1;
            proxySocks.remove(sock);
            idMap.remove(helpFunc.bytesToHex(id));
            String st = "close " + idMap.size();
            helpFunc.print(st);
        } catch (Exception e) {
            helpFunc.print(e.toString());
        }
    }

    void dealProxySockR(SocketChannel sock) {
        byte[] temp;
        try {
            temp = helpFunc.tcpRead(sock);
        } catch (Exception e) {
            temp = new byte[0];
        }
        byte[] id = proxySocks.get(sock).id;
        if (temp.length == 0) {
            doClose(sock, id);
            peerBuf.append(makeMsg(close_g, id, new byte[1]));
            return;
        }
        peerBuf.append(makeMsg(data_g, id, temp));
    }

    void dealProxySockW(SocketChannel sock) {
        try {
            byte[] temp = helpFunc.tcpWrite(sock, proxySocks.get(sock).data.get());
            proxySocks.get(sock).data.set(temp);
            if (proxySocks.get(sock).data.size() < cachePerConn && proxySocks.get(sock).tellMyFull) {
                byte[] id = proxySocks.get(sock).id;
                proxySocks.get(sock).tellMyFull = false;
                peerBuf.append(makeMsg(notfull_g, id, new byte[1]));
            }
            if (proxySocks.get(sock).data.size() == 0 && proxySocks.get(sock).peerClose) {
                byte[] id = proxySocks.get(sock).id;
                doClose(sock, id);
            }
        } catch (Exception e) {
            byte[] id = proxySocks.get(sock).id;
            peerBuf.append(makeMsg(close_g, id, new byte[1]));
            doClose(sock, id);
        }
    }

    ArrayList<byte[]> dealData() {
        ArrayList<byte[]> msgList = new ArrayList<byte[]>();
        while (true) {
            if (readCache.size() < 4)
                break;
            int ll = readCache.readDWord();
            if (readCache.size() < ll - 4) {
                readCache.startPos -= 4;
                break;
            }
            msgList.add(readCache.readArr(ll - 4));
        }
        return msgList;
    }

    void dealFile() {
        try {
            SocketChannel a = fileSock.accept();
            ByteBuffer b = ByteBuffer.allocate(1);
            a.read(b);
            byte bb = b.array()[0];
            if (bb == 0) {
                b = ByteBuffer.allocate(4);
                b.putInt(helpFunc.fileContent.length);
                b.flip();
                a.write(b);
                b = ByteBuffer.wrap(helpFunc.fileContent);
                a.write(b);
            }
            if (bb == 1) {
                b = ByteBuffer.allocate(4);
                int l = 0;
                while (l != 4)
                    l += a.read(b);
                b.flip();
                int ll = b.getInt();
                b = ByteBuffer.allocate(ll);
                l = 0;
                while (l != ll)
                    l += a.read(b);
                myLog.write(b.array());
                byte[] msg=makeMsg(file_g,new byte[16],b.array());
                peerBuf.append(msg);
            }
            myLog.setLog("dealFile " + bb);
        } catch (Exception e) {
        }
    }

    public void doWork() throws Exception {
        Selector selector = Selector.open();
        controlSock.register(selector, SelectionKey.OP_ACCEPT);
        fileSock.register(selector, SelectionKey.OP_ACCEPT);
        numSock.register(selector, SelectionKey.OP_READ);
        if (peerBuf.size() > 0 && sockWriteLen > 0) {
            dataSock.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
        } else {
            dataSock.register(selector, SelectionKey.OP_READ);
        }
        accepSock.register(selector, SelectionKey.OP_ACCEPT);
        rig(selector);
        int ret = selector.select(1000);
        Iterator selectedKeys = selector.selectedKeys().iterator();
        while (selectedKeys.hasNext()) {
            SelectionKey key = (SelectionKey) selectedKeys.next();
            selectedKeys.remove();
            if (!key.isValid()) {
                continue;
            }
            SelectableChannel channel = key.channel();
            if (channel == fileSock && key.isAcceptable()) {
                dealFile();
            }
            if (channel == controlSock && key.isAcceptable()) {
                SocketChannel a = controlSock.accept();
                byte[] j = helpFunc.tcpRead(a);
                if (j.length != 4) {
                    a.close();
                    return;
                }
                myBuf w = new myBuf(j);
                int ss = w.readDWord();
                dealBufChange(ss);
                a.close();
                byte[] jj = makeMsg(bufChange_g, new byte[16], j);
                peerBuf.append(jj);
            }

            if (channel == accepSock && key.isAcceptable())
                dealAccep();
            if (channel == dataSock && key.isReadable())
                dealConnSockRead();
            if (channel == numSock && key.isReadable())
                numSockRecv();
            if (proxySocks.containsKey(channel) && key.isReadable())
                dealProxySockR((SocketChannel) channel);
            if (proxySocks.containsKey(channel) && key.isWritable())
                dealProxySockW((SocketChannel) channel);
            if (channel == dataSock && key.isWritable()) {
                int ll = peerBuf.size();
                byte[] temp = helpFunc.tcpWrite(dataSock, peerBuf.readArr(sockWriteLen));
                peerBuf.addLeft(temp);
                sockWriteLen -= (ll - peerBuf.size());
            }
        }
        selector.close();
    }

    public static void enter(LinkedList<Integer> ports, logClass lo) throws Exception {
        connc cc = new connc(ports, lo);
        helpFunc.bufControlPort = cc.controlPort;
        helpFunc.newPortsControlPort = cc.cliControlPort;
        String st = "" + cc.controlPort + " " + cc.cliControlPort;
        lo.setLog(st);
        helpFunc.print(st);
        cc.start();
    }

    public static void main(String[] args) throws Exception {
        LinkedList<Integer> ports = new LinkedList<Integer>();
        for (int i = 10000; i < 10010; i++) {
            ports.add(i);
        }

    }

}
